// posix header
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>

// openMP header
#include <mpi.h>
#include <omp.h>

// c++ header
#include <algorithm>
#include <fstream>
#include <iostream>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <thread>
#include <vector>

using namespace std;

int mpi_rank, mpi_size;

std::string one_dot(".");
std::string tow_dot("..");

const string kLatStartPattern = " lat=\"";
const string kLonStartPattern = "\" lon=\"";
const string kLonEndPattern = "\">";

void process_dir(std::string input_dir, std::string output_file) {
  // 这三个变量只有这里才会被用到
  // 输入输出流
  ofstream ofs;
  ifstream ifs;
  ofs.open(output_file);

  if (ofs.fail()) {
    cerr << "can't open file " << output_file << endl;
    std::exit(1);
  }

  cout << "Convert files from: " << input_dir << " to: " << output_file << endl;

  DIR* input_dir_open;  //定义一个目录指针
  if ((input_dir_open = opendir(input_dir.c_str())) == NULL) {
    cerr << "It's an invalid dir:" << input_dir << endl;
    std::abort();
  }

  /* 没问题的话开始遍历dir */
  struct dirent* direct_dirp;
  while ((direct_dirp = readdir(input_dir_open)) !=
         NULL) {  //最后一步了，遍历最里层文件夹

    if (direct_dirp->d_type == DT_REG && direct_dirp->d_name != one_dot &&
        direct_dirp->d_name != tow_dot) {
      // 得到文件绝对路径

      string input_file = input_dir + "/" + direct_dirp->d_name;

      ifs.open(input_file);

      string line;
      while (getline(ifs, line)) {
        std::regex reg(".*<trkpt lat=.*lon=.*>.*");
        if (std::regex_match(line, reg)) {
          // int lat_prev = line.find(kLatStartPattern);
          // int lat_begin = lat_prev + kLatStartPattern.size();
          // int lat_end = line.find(kLonStartPattern);

          // auto lat_str = line.substr(lat_begin, lat_end - lat_begin);

          // int lon_prev = line.find(kLonStartPattern);
          // int lon_begin = lon_prev + kLonStartPattern.size();
          // int lon_end = line.find(kLonEndPattern);

          // auto lon_str = line.substr(lon_begin, lon_end - lon_begin);

          // ofs << lat_str << " " << lon_str << endl;

          regex pattern("[-+]?([0-9]*\\.[0-9]+|[0-9]+)");
          smatch result;
          auto iter = line.cbegin();

          regex_search(iter, line.cend(), result, pattern);
          auto lat = result[0].str();

          iter = result[0].second;
          regex_search(iter, line.cend(), result, pattern);
          auto lon = result[0].str();

          ofs << lat << " " << lon << endl;

          if (lat.size() < 0 || lon.size() < 0) {
            cout << "Parsing error ...." << endl;
            MPI_Abort(MPI_COMM_WORLD, 1);
          }
        }
      }

      ifs.close();

    } else if (direct_dirp->d_name == one_dot ||
               direct_dirp->d_name == tow_dot) {
      //			cout << "omitting . and/or .." << endl;    //
      //直接文件夹（二级文件夹）也要考虑. ..目录项
    } else {
      cout << "error 2nd level directory..." << endl;
      std::abort();
    }
  }

  ofs.close();
}

void process_basedir(std::string input_basedir, std::string output_dir,
                     string output_prefix) {
  /* 同样，打开前先判断 */
  DIR* in_subdir_open;
  if ((in_subdir_open = opendir(input_basedir.c_str())) == NULL) {
    cout << "in valid insub dir:" << input_basedir << endl;
    std::abort();
  }

  struct dirent* subdir_p;
  vector<std::string> all_input_dirs;
  while ((subdir_p = readdir(in_subdir_open)) != NULL) {
    if (subdir_p->d_type == DT_DIR && subdir_p->d_name != one_dot &&
        subdir_p->d_name != tow_dot) {  // subdir也要单独掕出 . ..

      all_input_dirs.push_back(subdir_p->d_name);
    } else if (subdir_p->d_name == one_dot || subdir_p->d_name == tow_dot) {
      //			cout << "omitting . and/or .." << endl;
    } else {
      cout << "error 2nd level directory..." << endl;
      std::abort();
    }
  }

  int n = all_input_dirs.size();
  // sort to assure
  sort(all_input_dirs.begin(), all_input_dirs.end());

  int count = n / mpi_size;
  int remainder = n % mpi_size;

  int start;
  if (mpi_rank < remainder) {
    count += 1;
    start = count * mpi_rank;
  } else {
    start = count * mpi_rank + remainder;
  }

  int total_count;
  MPI_Allreduce(&count, &total_count, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
  if (total_count != n) {
    std::exit(1);
  }

  if (count == 0) {
    clog << "I do nothing..." << endl;
  }

#pragma omp parallel for
  for (int i = start; i < start + count; ++i) {
    const std::string& dirname = all_input_dirs[i];  //少用auto

    string input_dir = input_basedir + "/" + dirname;
    auto output_file = output_dir + "/" + output_prefix + dirname + ".txt";

    process_dir(input_dir, output_file);
  }
}

/* 0. dataset级别。
 * 大文件夹的输入输出接口*/
int process_dataset(string dataset_path, const string output_dir) {
  // check if the file exists
  DIR* dataset_dir_open;
  if ((dataset_dir_open = opendir(dataset_path.c_str())) == NULL) {
    cout << "in valid dataset dir:" << dataset_path << endl;
    std::abort();
  }

  if (mpi_rank == 0) {
    auto output_dir_open = opendir(output_dir.c_str());
    if (output_dir_open == NULL) {
      mkdir(output_dir.c_str(), ACCESSPERMS);
    }
  }

  struct dirent* basedir_open;
  while ((basedir_open = readdir(dataset_dir_open)) != NULL) {  //遍历
    if (basedir_open->d_type == DT_DIR && basedir_open->d_name != one_dot &&
        basedir_open->d_name != tow_dot) {
      //
      string output_prefix = basedir_open->d_name;
      string input_basedir = dataset_path + "/" + basedir_open->d_name;

      /* process处理的是indirect dir，将inputdir里面的所有文件整理到outputdir */
      process_basedir(input_basedir, output_dir, output_prefix);

    } else if (basedir_open->d_name == one_dot ||
               basedir_open->d_name == tow_dot) {
      //			cout << "omitting . and/or .." << endl;
    } else {
      cout << "Error 1st level directory..." << endl;
      std::abort();
    }
  }

  return 0;
}

int main(int argc, char* argv[]) {
  MPI_Init(&argc, &argv);
  MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank);
  MPI_Comm_size(MPI_COMM_WORLD, &mpi_size);

  if (argc < 2) {
    cout << "Enter the base directory..." << endl;
    std::abort();
  }

  /* 输入、输出文件夹 */
  string dataset_path = argv[1];

  int len = dataset_path.size();

  string output_dir;

  if (dataset_path[len - 1] == '/') {
    output_dir = dataset_path.substr(0, len - 1) + "_output_mpi";
  } else {
    output_dir = dataset_path + "_output_mpi";
  }

  cout << "output dir is: " << output_dir << endl;

  process_dataset(dataset_path, output_dir);

  MPI_Finalize();
  return 0;
}
